package dnl.jexem.camouflaj;

import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dnl.jexem.cmd.Command;
import dnl.jexem.cmd.CommandExecution;
import dnl.jexem.cmd.CommandExecutionFactory;

/**
 * A <code>RemoteCommandManager</code> is responsible for placing and retriving
 * commands from a remote location. It is designed to be used both by the agent
 * and the client.
 * 
 * @author Daniel Orr
 * 
 */
public abstract class RemoteCommandManager {

	private static final Logger logger = LoggerFactory.getLogger(RemoteCommandManager.class
			.getSimpleName());

	private boolean encryptCommands;

	/*
	 * The last time a command was executed.
	 */
	private Date lastCommandTimestamp;

	/**
	 * Places a new <code>Command</code> in the remote location managed by this
	 * <code>RemoteCommandManager</code>.
	 * 
	 * @param commandInfo
	 * @return commandId the id assigned by this
	 *         <code>RemoteCommandManager</code> to the command.
	 */
	public abstract String placeCommand(Command command);

	/**
	 * Sends the summary of the execution.
	 * 
	 * @param executionSummary
	 */
	public abstract void sendExecutionSummary(ExecutionSummary executionSummary);

	/**
	 * Reads the remote <code>ExecutionSummary</code>.
	 * 
	 * @param commandId
	 *            TODO
	 * @return
	 */
	public abstract ExecutionSummary readExecutionSummary(String commandId);

	/**
	 * Waits for the agent to acknowledge command retrieval.
	 * 
	 * @param commandId
	 *            TODO
	 * @param timeoutSec TODO
	 * @return TODO
	 */
	public abstract boolean waitForAcknowledge(String commandId, long timeoutSec);

	/**
	 * Waits for the agent to complete command execution.
	 * 
	 * @param commandId
	 *            TODO
	 * @return TODO
	 */
	public abstract boolean waitForExecution(String commandId);

	/**
	 * Retrives any pending commands and sends ACK for each.
	 * 
	 * @return
	 */
	public abstract CommandInfo[] retrievePendingCommands();

	public void setEncryptCommands(boolean encryptCommands) {
		this.encryptCommands = encryptCommands;
	}

	public boolean encryptCommands() {
		return encryptCommands;
	}

	/**
	 * 
	 * @return
	 */
	public boolean executeCommands() {
		boolean someCommandsExecuted = false;
		CommandInfo[] retrievePendingCommands = retrievePendingCommands();
		for (CommandInfo commandInfo : retrievePendingCommands) {
			if (executeCommand(commandInfo)) {
				someCommandsExecuted = true;
			}
		}
		return someCommandsExecuted;
	}

	/**
	 * 
	 * @return <code>true</code> if a command was executed.
	 */
	public boolean executeCommand(CommandInfo commandInfo) {
		if (commandInfo == null) {
			return false;
		}
		if (lastCommandTimestamp != null && !commandInfo.getTimestamp().after(lastCommandTimestamp)) {
			return false;
		}
		logger.info("Got a new command: " + commandInfo.getCommand().getCommand());
		Command command = commandInfo.getCommand();
		lastCommandTimestamp = commandInfo.getTimestamp();
		CommandExecution commandExecution = CommandExecutionFactory
				.createLocalCommandExecution(command);
		commandExecution.executeCommand(true);
		ExecutionSummary executionSummary = new ExecutionSummary();
		executionSummary.setCommandId(commandInfo.getCommandId());
		executionSummary.setOutput(commandExecution.getOutput());
		executionSummary.setErrorOutput(commandExecution.getErrorOutput());
		executionSummary.setExitStatus(commandExecution.getExitStatus());
		executionSummary.setExceptions(StringUtils.join(commandExecution.getExceptionMessages(), "\n"));
		sendExecutionSummary(executionSummary);
		return true;
	}

}
